///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.rectangular
{
  import flash.display.DisplayObject;
  import flash.display.DisplayObjectContainer;
  import flash.display.InteractiveObject;
  import flash.geom.Rectangle;
  
  import ilog.gauges.gaugesClasses.GaugeBase;
  import ilog.gauges.gaugesClasses.GaugeElement;
  import ilog.gauges.gaugesClasses.rectangular.RectangularGaugeElement;
  import ilog.utils.CSSUtil;
  import ilog.utils.RectangleUtil;
  
  import mx.controls.Label;
  import mx.core.EdgeMetrics;
  import mx.core.IBorder;
  import mx.core.IFlexDisplayObject;
  import mx.core.UIComponent;
  import mx.styles.CSSStyleDeclaration;

  /**
   * Number of pixels between areas in the horizontal direction.
   *  
   * @default 8
   */
  [Style(name="horizontalGap", type="Number", format="Length", inherit="no")]

  /**
   * Number of pixels between areas in the vertical direction.
   * 
   * @default 6
   */
  [Style(name="verticalGap", type="Number", format="Length", inherit="no")]      

  /**
   * Name of the class to use as the default skin for the background and border.
   * This applies only to the area covered by the visual elements, not to the title area.  
   *
   * @default null
   */
  [Style(name="backgroundSkin", type="Class", inherit="no")]
  
  /**
   * Name of the class to use as the default skin for the foreground. 
   * This applies only to the area covered by the visual elements, not to the title area.  
   *    
   * @default null
   */
  [Style(name="foregroundSkin", type="Class", inherit="no")]  

  /**
   * The <code>RectangularGauge</code> class is the top-level class of the ILOG Elixir
   * Rectangular Gauges Framework. It can be used to build custom horizontal
   * or vertical gauges in MXML or ActionScript.
   * <p>
   * Alternatively, ILOG Elixir provides predefined horizontal and vertical gauges in the
   * <code>ilog.gauges.controls</code> subpackages. 
   * </p>
   * <p>
   * A rectangular gauge is defined by a set of logical scales and a set of visual 
   * elements, such as <code>RectangularScaleRenderer</code>, <code>RectangleRenderer</code>, 
   * <code>RectangularTrackRenderer</code>, or <code>MarkerRenderer</code>.
   * </p>
   * The <code>RectangularGauge</code> sets the position and size of all the visual 
   * elements based on their <code>x</code>, <code>y</code>, <code>width</code>, 
   * and <code>height</code> properties, as well as on the <code>area</code> property.
   * The <code>area</code> property defines five areas to lay out the elements into:
   * <ul>
   *   <li>the left area</li>
   *   <li>the right area</li>
   *   <li>the top area</li>
   *   <li>the bottom area</li>
   *   <li>the center area (default)</li>
   * </ul>
   * <p>
   * The side areas are commonly used for fixed-size objects, such as labels
   * or indicators. The center area, taking the remaining place, is used
   * by the core gauge elements, such as scale, track, or value renderers.
   * </p> 
   * <p>
   * First, the side areas are sized so that elements in the left and right, on one 
   * hand, and top and bottom, on the other hand, can fit respectively their 
   * measured width and height in them.
   * </p>
   * <p>
   * Then, the elements in the side areas are positioned and sized according to 
   * their <code>x</code>, <code>y</code>, <code>width</code>, and <code>height</code>
   * properties. The percentage values are relative to the bounds of the corresponding
   * area, computed in the first step.
   * </p>
   * <p>
   * Lastly, the remaining area in the center of the gauge is used to lay out the
   * elements in this area. If the gauge has no explicit width, either in 
   * pixels or in percentage, the center area will have a width computed from the 
   * measured width of the elements in this area. Similarly, if the gauge has no 
   * explicit height, either in pixels or in percentage, the center area will have a 
   * height computed from the measured height of the elements in this area. If the 
   * gauge has an explicit width (or height), the elements in the center take as 
   * much width (or height) as is available. They are never bigger than their 
   * <code>maxWidth</code> (or <code>maxHeight</code>), so you can use these 
   * properties to avoid scaling them too much, if there is too much space
   * available in the center.
   * </p>
   * <p>
   * External assets can be managed using the <code>RectangularGaugeAsset</code> class,   
   * which is a visual element.
   * </p>
   * <p>
   * The <i>scales</i> array contains logical scales. For rectangular gauges, 
   * scales have to implement the <code>IRectangularGauge</code> interface, for example,
   * <code>RectangularLinearScale</code> or <code>RectangularCategoryScale</code>.
   * </p>
   * <p>
   * The <i>elements</i> array contains the visual elements.
   * </p>
   * 
   * @mxml
   *  
   * <p>The <code>&lt;ilog:RectangularGauge&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:RectangularGauge
   *   <strong>Properties</strong>
   *   backgroundFilter="[]"
   *   direction="horizontal|vertical"
   * 
   *   <strong>Styles</strong>
   *   showTitle="true|false"
   *   horizontalGap="8"
   *   verticalGap="6"
   *   backgroundSkin="<i>Class; null</i>"
   *   foregroundSkin="<i>Class; null</i>" 
   *  /&gt;
   * </pre>
   *
   *  
   * @see ilog.gauges.gaugesClasses.rectangular.IRectangularScale
   * @see ilog.gauges.gaugesClasses.rectangular.RectangularGaugeElement
   * 
   * @includeExample examples/RectangularGaugeExample.mxml -noswf
   */  
  public class RectangularGauge extends GaugeBase
  {
    private static var stylesInited:Boolean = initStyles(); 

    private static function initStyles():Boolean
    {
      var style:CSSStyleDeclaration =
        CSSUtil.createSelector("RectangularGauge");   
            
      style.defaultFactory = function():void {
        this.horizontalGap = 8;
        this.verticalGap = 6;         
      }
      
      return true;
    }
    
    private var _labelField:Label;        
    private var _holder:UIComponent;
    private var _elementsDirty:Boolean;
    private var _titleDirty:Boolean;
    
    /**
     * Constructor
     */
    public function RectangularGauge()
    {
      super();
    }
    
    private var _direction:String = "horizontal";

    [Inspectable(category="Gauge", defaultValue="horizontal", enumeration="horizontal,vertical", type="String")]    
     
    /**
     * The direction of the gauge. It can be <code>"horizontal"</code> (default),  
     * in which case the gauge goes from left to right, or <code>"vertical"</code>,
     * in which case the gauge goes from bottom to top.
     * 
     * @default "horizontal"
     */
    public function get direction():String 
    {
      return _direction;
    }
    
    /**
     * @private
     */
    public function set direction(value:String):void 
    {
      _direction = value;
      invalidateDisplayList();
    }
    
    private var _backgroundFilters:Array = [];   
    
    [Inspectable(category="Style")]    
    
    /**
     * An indexed array that contains each filter object associated with the 
     * background of the gauge. These filters are applied to the 
     * <code>backgroundSkin</code>, if any.
     * 
     * @default []
     */
    public function get backgroundFilters():Array 
    {
      return _backgroundFilters; 
    }
    
    /**
     * @private
     */
    public function set backgroundFilters(filters:Array):void 
    {
      _backgroundFilters = filters;
      if (currentBackSkin) {
        DisplayObject(currentBackSkin).filters = _backgroundFilters;
      } 
    }        
    
    /**
     * @private
     */
    public override function set title(value:String):void 
    {          
      super.title = value;
      _titleDirty = true;      
      invalidateProperties();      
    }    

    /**
     * @inheritDoc
     */
    public override function invalidateChildren():void
    {
      _elementsDirty = true;
      super.invalidateChildren();
    }
        
    /**
     * @inheritDoc
     */    
    protected override function getElementHolder(element:GaugeElement):UIComponent
    {
      if (element == null)
        return _holder;
      var elt:RectangularGaugeElement = element as RectangularGaugeElement;
      switch (elt.area) {
        case "top":
        case "bottom":
        case "left":
        case "right":
          return this;
        case "center":
        default:
          return _holder;
      }
    }
    
    /**
     * @private
     */
    protected override function commitProperties():void 
    {
      var invalidChildrenOrderer:Boolean = false;
      if (_elementsDirty) {
        _elementsDirty = false; 
        clearElements();
        invalidChildrenOrderer = true;
      }
      super.commitProperties();  
      if (_titleDirty) {
        _titleDirty = false;        
        if (getStyle("showTitle") == true) {
          _labelField.text = title;
          invalidateSize();
        }
        invalidChildrenOrderer = true;
      }      
      if (invalidChildrenOrderer && currentForeSkin != null) {
        // make sure it is still the latest element
        addChild(currentForeSkin as DisplayObject);
      }           
    }      
    
    /**
     * @private
     */
    protected override function createChildren():void
    {
      if (_holder == null) {
        _holder = new GaugeElementHolder();
        addChild(_holder);
      }
    }
    
    /**
     * @private 
     */ 
    override public function styleChanged(styleProp:String):void
    {
      super.styleChanged(styleProp);
      if (styleProp == null || styleProp == "showTitle") {
        if (getStyle("showTitle") == true) {
          if (_labelField == null) {
            _labelField = new Label();    
          }
          // we might have a pending label to set
          _labelField.text = title;
          if (_labelField.parent != this)
            addChild(_labelField); 
        } else {
          if (_labelField != null && _labelField.parent == this)
            removeChild(_labelField); 
        }
      }
      if (styleProp == null || styleProp == "titleStyleName") {
        if (_labelField != null) {
          _labelField.styleName = getStyle("titleStyleName");
        }
      }
      var skin:DisplayObject;
      var skinClass:Class;
      if (styleProp == null || styleProp == "backgroundSkin") {
        skinClass = getStyle("backgroundSkin");       
        if (currentBackSkin != null) {
          removeChild(currentBackSkin as DisplayObject); 
        }
        if (skinClass != null) {
          skin = new skinClass();
          skin.name = "backgroundSkin";
          skin.filters = _backgroundFilters;
          // always at bottom  
          addChildAt(skin, 0);
        }
        if (initialized)
          invalidateSize();
      }
      if (styleProp == null || styleProp == "foregroundSkin") {
        skinClass = getStyle("foregroundSkin");       
        if (currentForeSkin != null) {
          removeChild(currentForeSkin as DisplayObject); 
        }
        if (skinClass != null) {          
          skin = new skinClass();
          // make sure the forgeground skin will not catch events
          if (skin is InteractiveObject)
            InteractiveObject(skin).mouseEnabled = false;
          if (skin is DisplayObjectContainer)
            DisplayObjectContainer(skin).mouseChildren = false;
          skin.name = "foregroundSkin";
          // always at top  
          addChild(skin);
        }
        if (initialized)
          invalidateSize();
      }            
    }        
    
    /**
     * @private
     */
    protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);

      var x:Number = getStyle("paddingLeft");
      var y:Number = getStyle("paddingTop");
      var w:Number = unscaledWidth - x - getStyle("paddingRight");
      var h:Number = unscaledHeight - y - getStyle("paddingBottom");
      
      var hGap:Number = getStyle("horizontalGap");
      var vGap:Number = getStyle("verticalGap");
      
      if (getStyle("showTitle") == true && title != null) {
        var labelHeight:Number = _labelField.getExplicitOrMeasuredHeight() + titleGap;
        h -= labelHeight;
        if (titlePlacement == "top") {
          y += labelHeight;
          _labelField.move(0, 0);
        } else {
          _labelField.move(0, y + h + titleGap);  
        }
        _labelField.setActualSize(unscaledWidth, _labelField.getExplicitOrMeasuredHeight())        
      }

      // make sure we do not go out of bounds
      x = Math.min(unscaledWidth, Math.max(0, x));
      y = Math.min(unscaledHeight, Math.max(0, y));
      w = Math.max(0, w);
      h = Math.max(0, h);      
            
      // skin coordinates
      var sx:Number = x;
      var sy:Number = y;
      var sw:Number = w;
      var sh:Number = h;
      
      // background padding      
      if (currentBackSkin != null && currentBackSkin is IBorder) {
        var borderMetrics:EdgeMetrics = IBorder(currentBackSkin).borderMetrics;
        x += borderMetrics.left;
        y += borderMetrics.top;
        w -= borderMetrics.left + borderMetrics.right;
        h -= borderMetrics.top + borderMetrics.bottom;
        // make sure we do not go out of bounds
        x = Math.min(unscaledWidth, Math.max(0, x));
        y = Math.min(unscaledHeight, Math.max(0, y));
        w = Math.max(0, w);
        h = Math.max(0, h);                 
      }   
            
      // first try to find in which area each type visual element
      // must be layouted   
      var left:Rectangle = new Rectangle(x, y, 0, h);
      var right:Rectangle = new Rectangle(x + w, y, 0, h);
      var top:Rectangle = new Rectangle(x, y, w, 0);
      var bottom:Rectangle = new Rectangle(x, y + h, w, 0);
      var size:Object = { width : UIComponent.DEFAULT_MAX_WIDTH, 
                          height : UIComponent.DEFAULT_MAX_HEIGHT };
      var vMeasuredWidth:Number = 0;
      var hMeasuredHeight:Number = 0;                          
      
      for each (var element:RectangularGaugeElement in elements) {
        switch (element.area) {      
          case "top":
            top.height = Math.min(h, Math.max(top.height, element.getExplicitOrMeasuredHeight()));
            vMeasuredWidth = Math.max(vMeasuredWidth, element.getExplicitOrMeasuredWidth());
            left.y = right.y = top.height;
            left.height = right.height = h - top.height - bottom.height;
            right.y = top.height;
          break;
          case "bottom":       
            bottom.height = Math.min(h, Math.max(bottom.height, element.getExplicitOrMeasuredHeight()));
            bottom.y = Math.max(y, y + h - bottom.height);               
            vMeasuredWidth = Math.max(vMeasuredWidth, element.getExplicitOrMeasuredWidth());            
            left.height = right.height = h - top.height - bottom.height;         
          break;
          case "right":           
            right.width = Math.min(w, Math.max(right.width, element.getExplicitOrMeasuredWidth()));
            right.x = Math.max(x, x + w - right.width);             
            hMeasuredHeight = Math.max(hMeasuredHeight, element.getExplicitOrMeasuredHeight());            
          break;
          case "left":
            left.width = Math.min(w, Math.max(left.width, element.getExplicitOrMeasuredWidth()));
            hMeasuredHeight = Math.max(hMeasuredHeight, element.getExplicitOrMeasuredHeight());             
          break;
          case "center":
            computeMaxSizeForPercentage(element, size);
          default:
          break;
        }
      }          
          
      // remaining size once side objects have take their size
      var lhGap:Number = left.width != 0 ? hGap : 0;
      var rhGap:Number = right.width != 0 ? hGap : 0;
      var tvGap:Number = top.height != 0 ? vGap : 0;
      var bvGap:Number = bottom.height != 0 ? vGap : 0;
      // center coordinates
      var cx:Number = left.x + left.width + lhGap;
      var cy:Number = top.y + top.height + tvGap;
      var cw:Number = w - left.width - right.width - lhGap - rhGap;
      var ch:Number = h - top.height - bottom.height - tvGap - bvGap;
      
      var dx:Number, dy:Number;
      
      if (cw > size.width) {
        dx = (cw - size.width);
        // make sure we do not go below skin size and other stuff
        if (currentBackSkin != null && (sw - dx) < currentBackSkin.measuredWidth) {
          dx = sw - currentBackSkin.measuredWidth;
        }  
        cx += dx / 2;
        cw -= dx;
        // adjust skin
        sx += dx / 2;
        sw -= dx;
        // adjust others
        if (top != null) {
          top.x += dx / 2;
          top.width -= dx;
        }
        if (bottom != null) {
          bottom.x += dx / 2;
          bottom.width -= dx;
        } 
        if (left != null) {
          left.x += dx / 2;
        }
       if (right != null) {
          right.x -= dx / 2;
        }                                 
      } 
      if (ch > size.height) {
        dy = (ch - size.height);
        // make sure we do not go below skin size
        if (currentBackSkin != null && (sh - dy) < currentBackSkin.measuredHeight) {
          dy = sh - currentBackSkin.measuredHeight;
        }         
        cy += dy / 2;
        ch -= dy;
        // adjust skin
        sy += dy / 2;
        sh -= dy;
        // adjust others
        if (top != null) {
          top.y += dy / 2;
        }
        if (bottom != null) {
          bottom.y -= dy / 2;
        } 
        if (left != null) {
          left.y += dy / 2;
          left.height -= dy;
        }
       if (right != null) {
          right.y += dy / 2;
          right.height -= dy;
        }                         
      }
  
      // if we have not explicit size or percentage size let's not have
      // a center bigger than its measured size
      if (isNaN(explicitWidth) && isNaN(percentWidth) && cw > _holder.measuredWidth) {
        dx = (cw - _holder.measuredWidth);
        // make sure we do not go below skin size
        if (currentBackSkin != null && (sw - dx) < currentBackSkin.measuredWidth) {
          dx = sw - currentBackSkin.measuredWidth;
        }
        if ((top.width - dx) < vMeasuredWidth) {
          dx = top.width - vMeasuredWidth;
        }         
        // this just a measured size after so do not break side objects beceause of that...
        cx += dx / 2;
        cw -= dx;
        // adjust skin
        sx += dx / 2;
        sw -= dx;
        // adjust others        
        if (top != null) {
          top.x += dx / 2;
          top.width -= dx;
        }
        if (bottom != null) {
          bottom.x += dx / 2;
          bottom.width -= dx;
        } 
        if (left != null) {
          left.x += dx / 2;
        }
       if (right != null) {
          right.x -= dx / 2;
        }                            
      }    
      if (isNaN(explicitHeight) && isNaN(percentHeight) && ch > _holder.measuredHeight) {
        dy = (ch - _holder.measuredHeight);
        // make sure we do not go below skin size
        if (currentBackSkin != null && (sh - dy) < currentBackSkin.measuredHeight) {
          dy = sh - currentBackSkin.measuredHeight;
        }            
        if ((left.height - dy) < hMeasuredHeight) {
          dy = left.height - hMeasuredHeight;
        } 
        cy += dy / 2;
        ch -= dy;
        // adjust skin
        sy += dy / 2;
        sh -= dy;
        // adjust others
        if (top != null) {
          top.y += dy / 2;
        }
        if (bottom != null) {
          bottom.y -= dy / 2;
        } 
        if (left != null) {
          left.y += dy / 2;
          left.height -= dy;
        }
       if (right != null) {
          right.y += dy / 2;
          right.height -= dy;
        }                         
      }        
   
      if (currentBackSkin != null) {
        currentBackSkin.move(sx, sy);
        currentBackSkin.setActualSize(sw, sh);
      }

      // foreground      
      if (currentForeSkin != null) {
        currentForeSkin.move(sx, sy);
        currentForeSkin.setActualSize(sw, sh);
        // foreground skin is not defining borders
      }      
      
      // when someone explicitely ask for a smaller size than the smaller
      // min size
      cw = Math.max(0, cw);
      ch = Math.max(0, ch);
      _holder.setActualSize(cw, ch);
      _holder.move(cx, cy);     
      
      var bounds:Rectangle;
      var center:Rectangle = new Rectangle(0, 0, cw, ch);
      for each (element in elements) {         
        switch (element.area) {
          case "right":
            bounds = right;
          break;
          case "left":
            bounds = left;
          break;
          case "top":
            bounds = top;
          break;
          case "bottom":
            bounds = bottom;
          break;
          case "center":
          default:
            bounds = center;
        }
        bounds = RectangularGauge.computeSize(element, bounds);
        element.setActualSize(bounds.width, bounds.height);
        element.move(bounds.x, bounds.y);
      }   
    }
    
    /**
     * @private
     */
    override protected function measure():void 
    {
      super.measure();
      
      var width:Number = 0;
      var height:Number = 0;
      var minWidth:Number = 0;
      var minHeight:Number = 0;
      
      var hGap:Number = getStyle("horizontalGap");
      var vGap:Number = getStyle("verticalGap");      

      var leftWidth:Number = 0, leftHeight:Number = 0, leftMinWidth:Number = 0, leftMinHeight:Number = 0;
      var rightWidth:Number = 0, rightHeight:Number = 0, rightMinWidth:Number = 0, rightMinHeight:Number = 0;
      var topWidth:Number = 0, topHeight:Number = 0, topMinWidth:Number = 0, topMinHeight:Number = 0;
      var bottomWidth:Number = 0, bottomHeight:Number = 0, bottomMinWidth:Number = 0, bottomMinHeight:Number = 0;
        
      for each (var element:RectangularGaugeElement in elements) {
        switch (element.area) {
          case "left":
            leftWidth = Math.max(leftWidth, element.getExplicitOrMeasuredWidth() + hGap);          
            leftMinWidth = Math.max(leftMinWidth, element.minWidth + hGap);
            leftHeight =  Math.max(leftHeight, element.getExplicitOrMeasuredHeight());   
            leftMinHeight = Math.max(leftMinHeight, element.minHeight);
          break;
          case "right":
            rightWidth = Math.max(rightWidth, element.getExplicitOrMeasuredWidth() + hGap);          
            rightMinWidth = Math.max(rightMinWidth, element.minWidth + hGap);
            rightHeight =  Math.max(rightHeight, element.getExplicitOrMeasuredHeight());   
            rightMinHeight = Math.max(rightMinHeight, element.minHeight);
          break;
          case "top":
            topWidth = Math.max(topWidth, element.getExplicitOrMeasuredWidth());          
            topMinWidth = Math.max(topMinWidth, element.minWidth);
            topHeight =  Math.max(topHeight, element.getExplicitOrMeasuredHeight() + vGap);   
            topMinHeight = Math.max(topMinHeight, element.minHeight + vGap);
          break;
          case "bottom":
            bottomWidth = Math.max(bottomWidth, element.getExplicitOrMeasuredWidth());          
            bottomMinWidth = Math.max(bottomMinWidth, element.minWidth);
            bottomHeight =  Math.max(bottomHeight, element.getExplicitOrMeasuredHeight() + vGap);   
            bottomMinHeight = Math.max(bottomMinHeight, element.minHeight + vGap);
          break;
          case "middle":
          // ignore
        }
      }
        
      if (leftWidth != 0) {
        width += leftWidth;
        minWidth += leftMinWidth;
        height = Math.max(height, leftHeight);
        minHeight = Math.max(minHeight, leftMinHeight); 
      } 
  
      if (rightWidth != 0) {
        width += rightWidth;
        minWidth += rightMinWidth;
        height = Math.max(height, rightHeight);
        minHeight = Math.max(minHeight, rightMinHeight); 
      }  
       
      // center       
      width += _holder.measuredWidth;
      height = Math.max(_holder.measuredHeight, height);
      minWidth += _holder.measuredMinWidth;
      minHeight = Math.max(_holder.measuredMinHeight, minHeight);
      
      if (topHeight != 0) {
        width = Math.max(width, topWidth);
        minWidth = Math.max(minWidth, topMinWidth);
        height +=  topHeight;
        minHeight += topMinHeight;
      }
     
     if (bottomHeight != 0) {
        width = Math.max(width, bottomWidth);
        minWidth = Math.max(minWidth, bottomMinWidth);
        height +=  bottomHeight;
        minHeight += bottomMinHeight;
      } 
      
      var borderMetrics:EdgeMetrics;
      
      // skin
      if (currentBackSkin != null) {
        if (currentBackSkin is IBorder) {
          borderMetrics = IBorder(currentBackSkin).borderMetrics;
          width += borderMetrics.left + borderMetrics.right;
          minWidth += borderMetrics.left + borderMetrics.right;
          height += borderMetrics.top + borderMetrics.bottom;
          minHeight += borderMetrics.top + borderMetrics.bottom;
        }         
        width = Math.max(width, currentBackSkin.measuredWidth);
        height = Math.max(height, currentBackSkin.measuredHeight);
        minWidth = Math.max(minWidth, currentBackSkin.measuredWidth);
        minHeight = Math.max(minHeight, currentBackSkin.measuredHeight);        
      }
      
      // skin
      if (currentForeSkin != null) {               
        width = Math.max(width, currentForeSkin.measuredWidth);
        height = Math.max(height, currentForeSkin.measuredHeight);
        minWidth = Math.max(minWidth, currentForeSkin.measuredWidth);
        minHeight = Math.max(minHeight, currentForeSkin.measuredHeight);         
        // foreground skin is not defining borders        
      }
      
      if (getStyle("showTitle") == true && title != null) {
        width = Math.max(width, _labelField.getExplicitOrMeasuredWidth());
        height += _labelField.getExplicitOrMeasuredHeight();
      
        minWidth = Math.max(minWidth, _labelField.measuredMinWidth);
        minHeight += _labelField.measuredMinHeight;
        
        height += titleGap;      
        minHeight += titleGap;   
      }
      
      width += getStyle("paddingLeft") + getStyle("paddingRight");
      height += getStyle("paddingTop") + getStyle("paddingBottom");
      minHeight += getStyle("paddingTop") + getStyle("paddingBottom");
      minWidth += getStyle("paddingLeft") + getStyle("paddingRight");
      
      measuredWidth = width;
      measuredMinWidth = Math.max(minWidth, measuredMinWidth);
      measuredHeight = height;
      measuredMinHeight = Math.max(minHeight, measuredMinHeight);
    }
    
    private function clearElements():void
    {
      var elts:Array = [];
      var element:DisplayObject;
      for (var i:int = 0; i < numChildren; i++) {
        element = getChildAt(i);
        if (element is RectangularGaugeElement) 
          elts.push(element);
      }
      for each (element in elts) {
        removeChild(element); 
      }
    }

    private function get currentBackSkin():IFlexDisplayObject
    {
      return getChildByName("backgroundSkin") as IFlexDisplayObject;
    }
    
    private function get currentForeSkin():IFlexDisplayObject
    {
      return getChildByName("foregroundSkin") as IFlexDisplayObject;
    }        
    
    private static function computeSize(element:RectangularGaugeElement,
                                        bounds:Rectangle, measure:Boolean = false):Rectangle 
    {
      var size:Rectangle = new Rectangle(0, 0, 0, 0);
      if (isNaN(element.percentX)) {
        size.x = bounds.x + element.explicitX;
      } else {
        size.x = bounds.x + element.percentX * bounds.width / 100;
      }
      if (isNaN(element.percentY)) {
        size.y = bounds.y + element.explicitY;
      } else {
        size.y = bounds.y + element.percentY * bounds.height / 100;
      }      
      if (measure) {
        size.width = element.measuredWidth;
      } else if (isNaN(element.percentWidth)) { 
        size.width = element.getExplicitOrMeasuredWidth();
      } else {
        size.width = element.percentWidth * bounds.width / 100;
      }
      size.width = Math.min(size.width, element.maxWidth);
      size.width = Math.max(size.width, element.minWidth);
      if (measure) {
        size.height = element.measuredHeight;       
      } else if (isNaN(element.percentHeight)) {
        size.height = element.getExplicitOrMeasuredHeight();
      } else {
        size.height = element.percentHeight * bounds.height / 100;
      }    
      size.height = Math.min(size.height, element.maxHeight);
      size.height = Math.max(size.height, element.minHeight);  
      if (!measure) {
        size.width = Math.min(size.width, bounds.width);
        size.height = Math.min(size.height, bounds.height);
      }      
      return size;
    }
    
    /**
     * @private
     */
    internal static function computeHolderMinSize(elements:Array):Object
    {
      var size:Object = { width : 0, height : 0 };
      for each (var element:RectangularGaugeElement in elements) {
        computeRequiredHolderMinSizeForPercentage(element, size);
      }
      // percentages said we need at least to be 'size', let's do the layout
      // with that size if another constraint ask use for more room  
      var rectangle:Rectangle = new Rectangle(0, 0, 0, 0);      
      for each (element in elements) {
        RectangleUtil.add2(rectangle, computeSize(element, new Rectangle(0, 0, size.width, size.height), true));
      }           
      return { width : rectangle.width, height : rectangle.height };
    }

    private static function computeRequiredHolderMinSizeForPercentage(element:RectangularGaugeElement, size:Object):void    
    {      
      if (!isNaN(element.percentWidth) && !isNaN(element.minWidth)) {
        size.width = Math.max(size.width, element.minWidth * 100 / element.percentWidth);
      }
      if (!isNaN(element.percentHeight) && !isNaN(element.minHeight)) {
        size.height = Math.max(size.height, element.minHeight * 100 / element.percentHeight);
      }      
    }    
    
    private static function computeMaxSizeForPercentage(element:RectangularGaugeElement, size:Object):void    
    {      
      if (!isNaN(element.percentWidth) && !isNaN(element.maxWidth)) {
        size.width = Math.min(size.width, element.maxWidth * 100 / element.percentWidth);
      }
      if (!isNaN(element.percentHeight) && !isNaN(element.maxHeight)) {
        size.height = Math.min(size.height, element.maxHeight * 100 / element.percentHeight);
      }      
    }                           
  }
}
